package com.ruoyi.socket.ws;

import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.YesOrNo;
import com.ruoyi.common.utils.sign.Base64;
import com.ruoyi.kong.domain.KSbh;
import com.ruoyi.kong.service.IKSbhService;
import com.ruoyi.socket.ws.bean.LoginVo;
import com.ruoyi.socket.ws.bean.WsMsg;
import com.ruoyi.system.service.ISysUserService;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 消息推送(非单例)
 *
 * @author MaoMing
 * @date 2023/09/26
 */
@Slf4j
@Component
@ServerEndpoint(value = "/sysMsg/{token}")
public class WsSysMsg {

    private static RedisCache redisUtils;

    public static ConcurrentMap<String, Session> ChannelMap = new ConcurrentHashMap();

    public static ConcurrentMap<String, KSbh> sessionToAcNo = new ConcurrentHashMap();


    private static ISysUserService userService;



    private static IKSbhService ikSbhService;

    @Autowired
    public  void setIkSbhService(IKSbhService ikSbhService) {
        WsSysMsg.ikSbhService = ikSbhService;
    }

    @Autowired
    public void setUserService(ISysUserService userService) {
        WsSysMsg.userService = userService;
    }

    @Autowired
    public void setRedisUtils(RedisCache redisUtils) {
        WsSysMsg.redisUtils = redisUtils;
    }



    private Session currentSession;


    @OnOpen
    public void open(@PathParam("token") String token, Session session) {
        log.info("WebSocket-[消息]连接成功:{}", token);
        if (StringUtils.isBlank(token)) {
            log.error("WebSocket-[消息]连接失败:{}", token);
            this.close(session, AjaxResult.error("链接失败。").toString());
            return;
        }
        try{
            String json = new String(Base64.decode(token));
            LoginVo vo = JSON.parseObject(json, LoginVo.class);
            SysUser user = WsSysMsg.userService.selectUserById(Long.valueOf(vo.getUn()));
            if(user==null){
                this.close(session, AjaxResult.error("链接失败。").toString());
                return;
            }
            KSbh sbh = ikSbhService.selectKSbhByUidAndSbh(vo.getUn(),vo.getSbh());
            sbh.setOnLine(YesOrNo.YES.getValue());
            ikSbhService.updateKSbh(sbh);
            this.currentSession = session;
            log.info("WebSocket-[消息]登录成功:{}", user.getNickName());
            sessionToAcNo.put(session.getId(), sbh);
            ChannelMap.put( sbh.getGid().toString(), session);// 保存链接
        }catch (Exception e){
            log.error("WebSocket-[消息]连接失败:{}", e.getMessage());
            e.printStackTrace();
        }
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        if(!"heartbeat".equals(message)){
            this.closeConnection(session);
        }
        KSbh acNo = sessionToAcNo.get(session.getId());
        log.error("WebSocket-[消息]心跳接收{}---->{}",acNo,message);
        WsMsg<Date> wsMsg = new WsMsg<Date>();
        wsMsg.setData(new Date());
        wsMsg.setAc(-1);
        wsMsg.setAcNo(acNo.getUserId());
//        session.getBasicRemote().sendText(JSON.toJSONString(wsMsg));
        sendMsg(wsMsg);
    }


    public static void sendMsg(Session session, String msg) {
        if(session==null){
            log.error("WebSocket-[消息]发送消息失败,session is null");
            return;
        }
        synchronized (session) {
            try {
                session.getBasicRemote().sendText(msg);
            } catch (IOException e) {
                log.error("WebSocket-[消息]发送消息失败", e);
            }
        }
    }


    public static void sendMsgs(List<String> acNos, String msg) {
       for (String acNo : acNos){
           sendMsg(acNo,msg);
       }
    }

    public static void sendMsg(String acNo, String msg) {
        Session session = ChannelMap.get(acNo);
        sendMsg(session,msg);
    }

    public static <T>  void sendMsg(WsMsg<T> wsMsg) {
        sendMsg(wsMsg.getAcNo(), JSON.toJSONString(wsMsg));
    }

    /**
     * 关闭
     *
     * @param session
     * @param msg
     */
    private void close(Session session, String msg) {
        if(session==null){
            return;
        }
        if (!session.isOpen()) {
            return;
        }
        synchronized (session) {
            try {
                if (!session.isOpen()) {
                    return;
                }
                session.getBasicRemote().sendText(msg);
                closeConnection(session);
            } catch (IOException e) {
                log.error("WebSocket-[消息]发送消息失败(关闭连接)", e);
            }
        }
    }

    private void closeConnection(Session session)  {
        if(session==null){
            return;
        }
        Iterator<Map.Entry<String, Session>> iterator = ChannelMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Session> entry = iterator.next();
            if (entry.getValue().getId().equals(session.getId())) {
                iterator.remove();
            }
        }
        try {
            KSbh acNo = sessionToAcNo.get(session.getId());
            acNo.setOnLine(YesOrNo.NO.getValue());
            ikSbhService.updateKSbh(acNo);
            sessionToAcNo.remove(session.getId());
            session.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    @OnClose
    public void close() {
        this.closeConnection(this.currentSession);
        log.info("关闭会话");
    }

    @OnError
    public void close(Session session, Throwable error) {
        error.printStackTrace();
        this.closeConnection(this.currentSession);
    }

}